package com.inqbarna.tablefixheaders;

import com.inqbarna.tablefixheaders.adapters.TableAdapter;
import com.inqbarna.tablefixheaders.utils.LogUtil;
import com.inqbarna.tablefixheaders.utils.ResUtils;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.Image;
import ohos.agp.components.ScrollHelper;
import ohos.agp.components.VelocityDetector;
import ohos.agp.render.Canvas;
import ohos.app.Context;
import ohos.data.rdb.DataObserver;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;
import java.util.List;

/**
 * This view shows a table which can scroll in both directions. Also still
 * leaves the headers fixed.
 *
 * @author Brais Gabín (InQBarna)
 */
public class TableFixHeaders extends ComponentContainer
    implements Component.EstimateSizeListener, Component.DrawTask, Component.TouchEventListener,
    ComponentContainer.ArrangeListener {
    private final Image[] shadows;

    private final int shadowSize;

    private final int SHADOW_WIDTH = 25;

    private final int TOUCH_SLOP = 24;

    private final int MAX_VELOCITY = 18000;

    private final int MIN_VELOCITY = 150;

    private final int minimumVelocity;

    private final int maximumVelocity;

    private final Flinger flinger;

    private int currentX;

    private int currentY;

    private TableAdapter adapter;

    private int scrollX;

    private int scrollY;

    private int firstRow;

    private int firstColumn;

    private int[] widths;

    private int[] heights;

    @SuppressWarnings("unused")
    private Component headView;

    private List<Component> rowViewList;

    private List<Component> columnViewList;

    private List<List<Component>> bodyViewTable;

    private int rowCount;

    private int columnCount;

    private int width;

    private int height;

    private Recycler recycler;

    private TableAdapterDataSetObserver tableAdapterDataSetObserver;

    private boolean needRelayout;

    private VelocityDetector velocityTracker;

    private int touchSlop;

    EventRunner eventRunner = EventRunner.getMainEventRunner();

    EventHandler eventHandler = new EventHandler(eventRunner) {
        @Override
        public void processEvent(InnerEvent event) {
        }
    };

    /**
     * Simple constructor to use when creating a view from code.
     *
     * @param context The Context the view is running in, through which it can
     * access the current theme, resources, etc.
     */
    public TableFixHeaders(Context context) {
        this(context, null);
    }

    /**
     * Constructor that is called when inflating a view from XML. This is called
     * when a view is being constructed from an XML file, supplying attributes
     * that were specified in the XML file. This version uses a default style of
     * 0, so the only attribute values applied are those in the Context's Theme
     * and the given AttributeSet.
     * <p>
     * The method onFinishInflate() will be called after all children have been
     * added.
     *
     * @param context The Context the view is running in, through which it can
     * access the current theme, resources, etc.
     * @param attrs The attributes of the XML tag that is inflating the view.
     */
    public TableFixHeaders(Context context, AttrSet attrs) {
        super(context, attrs);
        setEstimateSizeListener(this::onEstimateSize);
        setTouchEventListener(this::onTouchEvent);
        setArrangeListener(this::onArrange);
        addDrawTask(this::onDraw);
        this.headView = null;
        this.rowViewList = new ArrayList<Component>();
        this.columnViewList = new ArrayList<Component>();
        this.bodyViewTable = new ArrayList<List<Component>>();

        this.needRelayout = true;

        LayoutConfig config = new LayoutConfig(LayoutConfig.MATCH_CONTENT, LayoutConfig.MATCH_CONTENT);
        config.width = SHADOW_WIDTH;
        this.shadows = new Image[4];
        this.shadows[0] = new Image(context);
        this.shadows[0].setBackground(
            ResUtils.getShapeElement(context, ResUtils.ANGLE_0, ResourceTable.Color_start_color,
                ResourceTable.Color_end_color));
        this.shadows[0].setLayoutConfig(config);
        this.shadows[1] = new Image(context);
        this.shadows[1].setBackground(
            ResUtils.getShapeElement(context, ResUtils.ANGLE_270, ResourceTable.Color_start_color,
                ResourceTable.Color_end_color));
        this.shadows[1].setLayoutConfig(config);
        this.shadows[2] = new Image(context);
        this.shadows[2].setBackground(
            ResUtils.getShapeElement(context, ResUtils.ANGLE_180, ResourceTable.Color_start_color,
                ResourceTable.Color_end_color));
        this.shadows[2].setLayoutConfig(config);
        this.shadows[3] = new Image(context);
        this.shadows[3].setBackground(
            ResUtils.getShapeElement(context, ResUtils.ANGLE_90, ResourceTable.Color_start_color,
                ResourceTable.Color_end_color));
        this.shadows[3].setLayoutConfig(config);

        this.shadowSize = (int) ResUtils.getDimen(context, ResourceTable.Float_shadow_size);

        this.flinger = new Flinger(context);
        this.touchSlop = TOUCH_SLOP;
        this.minimumVelocity = MIN_VELOCITY;
        this.maximumVelocity = MAX_VELOCITY;
    }

    /**
     * Returns the adapter currently associated with this widget.
     *
     * @return The adapter used to provide this view's content.
     */
    public TableAdapter getAdapter() {
        return adapter;
    }

    /**
     * Sets the data behind this TableFixHeaders.
     *
     * @param adapter The TableAdapter which is responsible for maintaining the data
     * backing this list and for producing a view to represent an
     * item in that data set.
     */
    public void setAdapter(TableAdapter adapter) {
        if (adapter == null) {
            this.adapter = null;
            setArrangeListener(null);
            addDrawTask(null);
            setEstimateSizeListener(null);
            setTouchEventListener(null);
            needRelayout = false;
            LogUtil.debug("set Adpater", "adapter is null");
            return;
        }
        if (this.adapter != null) {
            LogUtil.debug("set Adpater", "adapter not null");
            this.adapter.unregisterDataSetObserver(tableAdapterDataSetObserver);
        } else {
            LogUtil.debug("set Adpater", "adapter is null");

        }

        this.adapter = adapter;
        tableAdapterDataSetObserver = new TableAdapterDataSetObserver();
        this.adapter.registerDataSetObserver(tableAdapterDataSetObserver);

        this.recycler = new Recycler(adapter.getViewTypeCount());

        scrollX = 0;
        scrollY = 0;
        firstColumn = 0;
        firstRow = 0;

        needRelayout = true;
        postLayout();
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        if (velocityTracker == null) { // If we do not have velocity tracker
            velocityTracker = VelocityDetector.obtainInstance(); // then get one
        }
        velocityTracker.addEvent(event); // add this movement to it

        switch (event.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN: {
                if (!flinger.isFinished()) { // If scrolling, then stop now
                    flinger.forceFinished();
                }
                currentX = (int) event.getPointerPosition(0).getX();
                currentY = (int) event.getPointerPosition(0).getY();
                break;
            }
            case TouchEvent.POINT_MOVE: {
                final int x2 = (int) event.getPointerPosition(0).getX();
                final int y2 = (int) event.getPointerPosition(0).getY();
                final int diffX = currentX - x2;
                final int diffY = currentY - y2;
                currentX = x2;
                currentY = y2;

                scrollBy(diffX, diffY);
                break;
            }
            case TouchEvent.PRIMARY_POINT_UP: {
                final VelocityDetector velocityTracker = this.velocityTracker;
                velocityTracker.calculateCurrentVelocity(1000, maximumVelocity, maximumVelocity);
                float velocity[] = velocityTracker.getVelocity();
                int velocityX = (int) velocity[0];
                int velocityY = (int) velocity[1];

                if (Math.abs(velocityX) > minimumVelocity || Math.abs(velocityY) > minimumVelocity) {
                    flinger.start(getActualScrollX(), getActualScrollY(), velocityX, velocityY, getMaxScrollX(),
                        getMaxScrollY());
                } else {
                    if (this.velocityTracker != null) { // If the velocity less than threshold
                        //this.velocityTracker.recycle(); // recycle the tracker
                        this.velocityTracker = null;
                    }
                }
                break;
            }
        }
        return true;
    }

    @Override
    public void scrollTo(int x, int y) {
        if (needRelayout) {
            scrollX = x;
            firstColumn = 0;

            scrollY = y;
            firstRow = 0;
        } else {
            scrollBy(x - sumArray(widths, 1, firstColumn) - scrollX, y - sumArray(heights, 1, firstRow) - scrollY);
        }
    }

    @Override
    public void scrollBy(int x, int y) {
        scrollX += x;
        scrollY += y;

        if (needRelayout) {
            return;
        }

        scrollBounds();

        /*
         * TODO Improve the algorithm. Think big diagonal movements. If we are
         * in the top left corner and scrollBy to the opposite corner. We will
         * have created the views from the top right corner on the X part and we
         * will have eliminated to generate the right at the Y.
         */
        if (scrollX == 0) {
            // no op
        } else if (scrollX > 0) {
            while (widths[firstColumn + 1] < scrollX) {
                if (!rowViewList.isEmpty()) {
                    removeLeft();
                }
                scrollX -= widths[firstColumn + 1];
                firstColumn++;
            }
            while (getFilledWidth() < width) {
                addRight();
            }
        } else {
            while (!rowViewList.isEmpty() && getFilledWidth() - widths[firstColumn + rowViewList.size()] >= width) {
                removeRight();
            }
            if (rowViewList.isEmpty()) {
                while (scrollX < 0) {
                    firstColumn--;
                    scrollX += widths[firstColumn + 1];
                }
                while (getFilledWidth() < width) {
                    addRight();
                }
            } else {
                while (0 > scrollX) {
                    addLeft();
                    firstColumn--;
                    scrollX += widths[firstColumn + 1];
                }
            }
        }

        if (scrollY == 0) {
            // no op
        } else if (scrollY > 0) {
            while (heights[firstRow + 1] < scrollY) {
                if (!columnViewList.isEmpty()) {
                    removeTop();
                }
                scrollY -= heights[firstRow + 1];
                firstRow++;
            }
            while (getFilledHeight() < height) {
                addBottom();
            }
        } else {
            while (!columnViewList.isEmpty()
                && getFilledHeight() - heights[firstRow + columnViewList.size()] >= height) {
                removeBottom();
            }
            if (columnViewList.isEmpty()) {
                while (scrollY < 0) {
                    firstRow--;
                    scrollY += heights[firstRow + 1];
                }
                while (getFilledHeight() < height) {
                    addBottom();
                }
            } else {
                while (0 > scrollY) {
                    addTop();
                    firstRow--;
                    scrollY += heights[firstRow + 1];
                }
            }
        }

        repositionViews();

        shadowsVisibility();

        enableScrollBar(AXIS_X, true);
    }

    public int getActualScrollX() {
        return scrollX + sumArray(widths, 1, firstColumn);
    }

    public int getActualScrollY() {
        return scrollY + sumArray(heights, 1, firstRow);
    }

    private int getMaxScrollX() {
        return Math.max(0, sumArray(widths) - width);
    }

    private int getMaxScrollY() {
        return Math.max(0, sumArray(heights) - height);
    }

    private int getFilledWidth() {
        return widths[0] + sumArray(widths, firstColumn + 1, rowViewList.size()) - scrollX;
    }

    private int getFilledHeight() {
        return heights[0] + sumArray(heights, firstRow + 1, columnViewList.size()) - scrollY;
    }

    private void addLeft() {
        addLeftOrRight(firstColumn - 1, 0);
    }

    private void addTop() {
        addTopAndBottom(firstRow - 1, 0);
    }

    private void addRight() {
        final int size = rowViewList.size();
        addLeftOrRight(firstColumn + size, size);
    }

    private void addBottom() {
        final int size = columnViewList.size();
        addTopAndBottom(firstRow + size, size);
    }

    private void addLeftOrRight(int column, int index) {
        Component view = makeView(-1, column, widths[column + 1], heights[0]);
        rowViewList.add(index, view);

        int i = firstRow;
        for (List<Component> list : bodyViewTable) {
            view = makeView(i, column, widths[column + 1], heights[i + 1]);
            list.add(index, view);
            i++;
        }
    }

    private void addTopAndBottom(int row, int index) {
        Component view = makeView(row, -1, widths[0], heights[row + 1]);
        columnViewList.add(index, view);

        List<Component> list = new ArrayList<Component>();
        final int size = rowViewList.size() + firstColumn;
        for (int i = firstColumn; i < size; i++) {
            view = makeView(row, i, widths[i + 1], heights[row + 1]);
            list.add(view);
        }
        bodyViewTable.add(index, list);
    }

    private void removeLeft() {
        removeLeftOrRight(0);
    }

    private void removeTop() {
        removeTopOrBottom(0);
    }

    private void removeRight() {
        removeLeftOrRight(rowViewList.size() - 1);
    }

    private void removeBottom() {
        removeTopOrBottom(columnViewList.size() - 1);
    }

    private void removeLeftOrRight(int position) {
        removeComponent(rowViewList.remove(position));
        for (List<Component> list : bodyViewTable) {
            removeComponent(list.remove(position));
        }
    }

    private void removeTopOrBottom(int position) {
        removeComponent(columnViewList.remove(position));
        List<Component> remove = bodyViewTable.remove(position);
        for (Component view : remove) {
            removeComponent(view);
        }
    }

    @Override
    public void removeComponent(Component view) {
        super.removeComponent(view);

        final int typeView = ((Tag) view.getTag()).tag_type_view;
        if (typeView != TableAdapter.IGNORE_ITEM_VIEW_TYPE) {
            recycler.addRecycledView(view, typeView);
        }
    }

    private void repositionViews() {
        int left, top, right, bottom, i;

        left = widths[0] - scrollX;
        i = firstColumn;
        for (Component view : rowViewList) {
            right = left + widths[++i];
            view.arrange(left, 0, right, heights[0]);
            left = right;
        }

        top = heights[0] - scrollY;
        i = firstRow;
        for (Component view : columnViewList) {
            bottom = top + heights[++i];
            view.arrange(0, top, widths[0], bottom);
            top = bottom;
        }

        top = heights[0] - scrollY;
        i = firstRow;
        for (List<Component> list : bodyViewTable) {
            bottom = top + heights[++i];
            left = widths[0] - scrollX;
            int j = firstColumn;
            for (Component view : list) {
                right = left + widths[++j];
                view.arrange(left, top, right, bottom);
                left = right;
            }
            top = bottom;
        }
        invalidate();
    }

    @Override
    public boolean onEstimateSize(int i, int i1) {
        onMeasure(i, i1);
        return false;
    }

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int widthMode = EstimateSpec.getMode(widthMeasureSpec);
        final int heightMode = EstimateSpec.getMode(heightMeasureSpec);
        final int widthSize = EstimateSpec.getSize(widthMeasureSpec);
        final int heightSize = EstimateSpec.getSize(heightMeasureSpec);
        final int w;
        final int h;
        if (adapter != null) {
            this.rowCount = adapter.getRowCount();
            this.columnCount = adapter.getColumnCount();

            widths = new int[columnCount + 1];
            for (int i = -1; i < columnCount; i++) {
                widths[i + 1] += adapter.getWidth(i);
            }
            heights = new int[rowCount + 1];
            for (int i = -1; i < rowCount; i++) {
                heights[i + 1] += adapter.getHeight(i);
            }

            if (widthMode == EstimateSpec.NOT_EXCEED) {
                w = Math.min(widthSize, sumArray(widths));
            } else if (widthMode == EstimateSpec.UNCONSTRAINT) {
                w = sumArray(widths);
            } else {
                w = widthSize;
                int sumArray = sumArray(widths);
                if (sumArray < widthSize) {
                    final float factor = widthSize / (float) sumArray;
                    for (int i = 1; i < widths.length; i++) {
                        widths[i] = Math.round(widths[i] * factor);
                    }
                    widths[0] = widthSize - sumArray(widths, 1, widths.length - 1);
                }
            }

            if (heightMode == EstimateSpec.NOT_EXCEED) {
                h = Math.min(heightSize, sumArray(heights));
            } else if (heightMode == EstimateSpec.UNCONSTRAINT) {
                h = sumArray(heights);
            } else {
                h = heightSize;
            }
        } else {
            if (heightMode == EstimateSpec.NOT_EXCEED || widthMode == EstimateSpec.UNCONSTRAINT) {
                w = 0;
                h = 0;
            } else {
                w = widthSize;
                h = heightSize;
            }
        }

        if (firstRow >= rowCount || getMaxScrollY() - getActualScrollY() < 0) {
            firstRow = 0;
            scrollY = Integer.MAX_VALUE;
        }
        if (firstColumn >= columnCount || getMaxScrollX() - getActualScrollX() < 0) {
            firstColumn = 0;
            scrollX = Integer.MAX_VALUE;
        }

        setEstimatedSize(w, h);
    }

    private int sumArray(int array[]) {
        if (array != null) {
            return sumArray(array, 0, array.length);
        }
        return -1;
    }

    private int sumArray(int array[], int firstIndex, int count) {
        int sum = 0;
        count += firstIndex;
        for (int i = firstIndex; i < count; i++) {
            sum += array[i];
        }
        return sum;
    }

    @Override
    public boolean onArrange(int i, int i1, int i2, int i3) {
        onLayout(true, i, i1, i2, i3);
        return false;
    }

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (needRelayout || changed) {

            needRelayout = false;
            resetTable();

            if (adapter != null) {
                width = r - l;
                height = b - t;

                int left, top, right, bottom;

                right = Math.min(width, sumArray(widths));
                bottom = Math.min(height, sumArray(heights));
                addShadow(shadows[0], widths[0], 0, widths[0] + shadowSize, bottom);
                addShadow(shadows[1], 0, heights[0], right, heights[0] + shadowSize);
                addShadow(shadows[2], right - shadowSize, 0, right, bottom);
                addShadow(shadows[3], 0, bottom - shadowSize, right, bottom);

                headView = makeAndSetup(-1, -1, 0, 0, widths[0], heights[0]);

                scrollBounds();
                adjustFirstCellsAndScroll();

                left = widths[0] - scrollX;
                for (int i = firstColumn; i < columnCount && left < width; i++) {
                    right = left + widths[i + 1];
                    final Component view = makeAndSetup(-1, i, left, 0, right, heights[0]);
                    rowViewList.add(view);
                    left = right;
                }

                top = heights[0] - scrollY;
                for (int i = firstRow; i < rowCount && top < height; i++) {
                    bottom = top + heights[i + 1];
                    final Component view = makeAndSetup(i, -1, 0, top, widths[0], bottom);
                    columnViewList.add(view);
                    top = bottom;
                }

                top = heights[0] - scrollY;
                for (int i = firstRow; i < rowCount && top < height; i++) {
                    bottom = top + heights[i + 1];
                    left = widths[0] - scrollX;
                    List<Component> list = new ArrayList<Component>();
                    for (int j = firstColumn; j < columnCount && left < width; j++) {
                        right = left + widths[j + 1];
                        final Component view = makeAndSetup(i, j, left, top, right, bottom);
                        list.add(view);
                        left = right;
                    }
                    bodyViewTable.add(list);
                    top = bottom;
                }

                shadowsVisibility();
            }
        }
    }

    private void scrollBounds() {
        scrollX = scrollBounds(scrollX, firstColumn, widths, width);
        scrollY = scrollBounds(scrollY, firstRow, heights, height);
    }

    private int scrollBounds(int desiredScroll, int firstCell, int sizes[], int viewSize) {
        if (desiredScroll == 0) {
            // no op
        } else if (desiredScroll < 0) {
            desiredScroll = Math.max(desiredScroll, -sumArray(sizes, 1, firstCell));
        } else {
            desiredScroll = Math.min(desiredScroll,
                Math.max(0, sumArray(sizes, firstCell + 1, sizes.length - 1 - firstCell) + sizes[0] - viewSize));
        }
        return desiredScroll;
    }

    private void adjustFirstCellsAndScroll() {
        int values[];

        values = adjustFirstCellsAndScroll(scrollX, firstColumn, widths);
        scrollX = values[0];
        firstColumn = values[1];

        values = adjustFirstCellsAndScroll(scrollY, firstRow, heights);
        scrollY = values[0];
        firstRow = values[1];
    }

    private int[] adjustFirstCellsAndScroll(int scroll, int firstCell, int sizes[]) {
        if (scroll == 0) {
            // no op
        } else if (scroll > 0) {
            while (sizes[firstCell + 1] < scroll) {
                firstCell++;
                scroll -= sizes[firstCell];
            }
        } else {
            while (scroll < 0) {
                scroll += sizes[firstCell];
                firstCell--;
            }
        }
        return new int[] {scroll, firstCell};
    }

    private void shadowsVisibility() {
        final int actualScrollX = getActualScrollX();
        final int actualScrollY = getActualScrollY();
        final int[] remainPixels = {
            actualScrollX, actualScrollY, getMaxScrollX() - actualScrollX, getMaxScrollY() - actualScrollY,
        };

        for (int i = 0; i < shadows.length; i++) {
            setAlpha(shadows[i], Math.min(remainPixels[i] / (float) shadowSize, 1));
        }
    }

    private void setAlpha(Image imageView, float alpha) {
        imageView.setAlpha(Math.round(alpha * 255));
    }

    private void addShadow(Image imageView, int l, int t, int r, int b) {
        imageView.arrange(l, t, r, b);
        addComponent(imageView);
    }

    private void resetTable() {
        headView = null;
        rowViewList.clear();
        columnViewList.clear();
        bodyViewTable.clear();

        removeAllComponents();
    }

    private Component makeAndSetup(int row, int column, int left, int top, int right, int bottom) {
        final Component view = makeView(row, column, right - left, bottom - top);
        view.arrange(left, top, right, bottom);
        return view;
    }

    @Override
    public void onDraw(Component child, Canvas canvas) {
        final boolean ret;

        Tag tag = (Tag) child.getTag();
        Integer row = -1;
        Integer column = -1;
        if (tag != null) {
            row = tag.tag_row;
            column = tag.tag_column;
        }
        // row == null => Shadow view
        if (row == null || (row == -1 && column == -1)) {
            canvas.restore();
        } else {
            canvas.save();
            if (row == -1) {
                canvas.clipRect(widths[0], 0, canvas.getLocalClipBounds().getWidth(),
                    canvas.getLocalClipBounds().getHeight());
            } else if (column == -1) {
                canvas.clipRect(0, heights[0], canvas.getLocalClipBounds().getWidth(),
                    canvas.getLocalClipBounds().getHeight());
            } else {
                canvas.clipRect(widths[0], heights[0], canvas.getLocalClipBounds().getWidth(),
                    canvas.getLocalClipBounds().getHeight());
            }
            canvas.restore();
        }

    }

    private Component makeView(int row, int column, int w, int h) {
        final int itemViewType = adapter.getItemViewType(row, column);
        final Component recycledView;
        if (itemViewType == TableAdapter.IGNORE_ITEM_VIEW_TYPE) {
            recycledView = null;
        } else {
            recycledView = recycler.getRecycledView(itemViewType);
        }
        final Component view = adapter.getView(row, column, recycledView, this);
        Tag tag = new Tag();
        tag.tag_type_view = itemViewType;
        tag.tag_column = column;
        tag.tag_row = row;
        view.setTag(tag);
        view.estimateSize(EstimateSpec.getSizeWithMode(w, EstimateSpec.PRECISE),
            EstimateSpec.getSizeWithMode(h, EstimateSpec.PRECISE));
        addTableView(view, row, column);
        return view;
    }

    private void addTableView(Component view, int row, int column) {
        if (row == -1 && column == -1) {
            addComponent(view, getChildCount() - 4);
        } else if (row == -1 || column == -1) {
            addComponent(view, getChildCount() - 5);
        } else {
            addComponent(view, 0);
        }
    }

    private class Tag {
        public int tag_row;

        public int tag_column;

        public int tag_type_view;
    }

    private class TableAdapterDataSetObserver implements DataObserver {

        @Override
        public void onChange() {
            needRelayout = true;
            postLayout();
        }
    }

    private class Flinger implements Runnable {
        private final ScrollHelper scroller;

        private int lastX = 0;

        private int lastY = 0;

        Flinger(Context context) {
            scroller = new ScrollHelper();
        }

        void start(int initX, int initY, int initialVelocityX, int initialVelocityY, int maxX, int maxY) {
            scroller.doFling(initX, initY, initialVelocityX, initialVelocityY, 0, maxX, 0, maxY);

            lastX = initX;
            lastY = initY;
            eventHandler.postTask(this);
        }

        public void run() {
            if (scroller.isFinished()) {
                return;
            }

            boolean more = scroller.updateScroll();
            int x = scroller.getScrollDistanceX();
            int y = scroller.getScrollDistanceY();
            int diffX = lastX - x;
            int diffY = lastY - y;
            if (diffX != 0 || diffY != 0) {
                scrollBy(diffX, diffY);
                lastX = x;
                lastY = y;
            }

            if (more) {
                eventHandler.postTask(this);
            }
        }

        boolean isFinished() {
            return scroller.isFinished();
        }

        void forceFinished() {
            if (!scroller.isFinished()) {
                //scroller.forceFinished(true); this need be replaced,when alternative api available.
            }
        }
    }
}
